package com.crois.barrier.web.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.server.WebServerException;
import org.springframework.util.ResourceUtils;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.SslProvider;

import java.io.IOException;
import java.net.URL;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @author Hou Ze Yu
 * @description
 * @date 2020/7/13
 */
@ConfigurationProperties("barrier.httpclient")
public class HttpClientProperties {
    /***
     * 连接超时时间
     */
    private Integer connectTimeout;
    /***
     * 响应超时时间
     */
    private Duration responseTimeout;

    private Pool pool =new Pool();

    private Proxy proxy =new Proxy();

    private Ssl ssl =new Ssl();

    /**
     * 是否 wiretap netty httpclient
     */
    private boolean wiretap;

    public Integer getConnectTimeout() {
        return connectTimeout;
    }

    public HttpClientProperties setConnectTimeout(Integer connectTimeout) {
        this.connectTimeout = connectTimeout;
        return this;
    }

    public Duration getResponseTimeout() {
        return responseTimeout;
    }

    public HttpClientProperties setResponseTimeout(Duration responseTimeout) {
        this.responseTimeout = responseTimeout;
        return this;
    }

    public Pool getPool() {
        return pool;
    }

    public HttpClientProperties setPool(Pool pool) {
        this.pool = pool;
        return this;
    }

    public Proxy getProxy() {
        return proxy;
    }

    public HttpClientProperties setProxy(Proxy proxy) {
        this.proxy = proxy;
        return this;
    }

    public Ssl getSsl() {
        return ssl;
    }

    public HttpClientProperties setSsl(Ssl ssl) {
        this.ssl = ssl;
        return this;
    }

    public boolean isWiretap() {
        return wiretap;
    }

    public HttpClientProperties setWiretap(boolean wiretap) {
        this.wiretap = wiretap;
        return this;
    }

    @Override
    public String toString() {
        return "HttpClientProperties{" +
                "connectTimeout=" + connectTimeout +
                ", responseTimeout=" + responseTimeout +
                ", pool=" + pool +
                ", proxy=" + proxy +
                ", ssl=" + ssl +
                ", wiretap=" + wiretap +
                '}';
    }

    /***
     * pool
     */
    public static class Pool{
        /***
         * httpclient pool type , default ELASTIC
         */
        private PoolType type = PoolType.ELASTIC;
        /***
         * channel pool name default proxy;
         */
        private String name = "proxy";
        /***
         * the max connection
         */
        private Integer maxConnections = ConnectionProvider.DEFAULT_POOL_MAX_CONNECTIONS;

        /***
         * acquire Timeout
          */
        private Long acquireTimeout = ConnectionProvider.DEFAULT_POOL_ACQUIRE_TIMEOUT;

        public PoolType getType() {
            return type;
        }

        public Pool setType(PoolType type) {
            this.type = type;
            return this;
        }

        public String getName() {
            return name;
        }

        public Pool setName(String name) {
            this.name = name;
            return this;
        }

        public Integer getMaxConnections() {
            return maxConnections;
        }

        public Pool setMaxConnections(Integer maxConnections) {
            this.maxConnections = maxConnections;
            return this;
        }

        public Long getAcquireTimeout() {
            return acquireTimeout;
        }

        public Pool setAcquireTimeout(Long acquireTimeout) {
            this.acquireTimeout = acquireTimeout;
            return this;
        }

        @Override
        public String toString() {
            return "Pool{" +
                    "type=" + type +
                    ", name='" + name + '\'' +
                    ", maxConnections=" + maxConnections +
                    ", acquireTimeout=" + acquireTimeout +
                    '}';
        }

        public enum PoolType{
            /***
             * ELASTIC pool
             */
            ELASTIC,
            /***
             *FIXED pool
             */
            FIXED,
            /***
             * DISABLED pool
             */
            DISABLED
        }
    }

    /**
     * proxy
     */
    public static class Proxy {
        /***
         * proxy host
         */
        private String host;
        /***
         * proxy port
         */
        private Integer port;
        /***
         * proxy username
         */
        private String username;
        /***
         * proxy password
         */
        private String password;
        /**
         * proxy nonProxyHostsPattern
         */
        private String nonProxyHostsPattern;

        public String getHost() {
            return host;
        }

        public Proxy setHost(String host) {
            this.host = host;
            return this;
        }

        public Integer getPort() {
            return port;
        }

        public Proxy setPort(Integer port) {
            this.port = port;
            return this;
        }

        public String getUsername() {
            return username;
        }

        public Proxy setUsername(String username) {
            this.username = username;
            return this;
        }

        public String getPassword() {
            return password;
        }

        public Proxy setPassword(String password) {
            this.password = password;
            return this;
        }

        public String getNonProxyHostsPattern() {
            return nonProxyHostsPattern;
        }

        public Proxy setNonProxyHostsPattern(String nonProxyHostsPattern) {
            this.nonProxyHostsPattern = nonProxyHostsPattern;
            return this;
        }

        @Override
        public String toString() {
            return "Proxy{" +
                    "host='" + host + '\'' +
                    ", port=" + port +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    ", nonProxyHostsPattern='" + nonProxyHostsPattern + '\'' +
                    '}';
        }
    }

    /**
     * Ssl
     */
    public class Ssl {

        /**
         * Installs the netty InsecureTrustManagerFactory. This is insecure and not
         * suitable for production.
         */
        private boolean useInsecureTrustManager;

        /**
         * Trusted certificates for verifying the remote endpoint's certificate.
         */
        private List<String> trustedX509Certificates = new ArrayList<>();

        // use netty default SSL timeouts
        /**
         * SSL handshake timeout. Default to 10000 ms
         */
        private Duration handshakeTimeout = Duration.ofMillis(10000);

        /**
         * SSL close_notify flush timeout. Default to 3000 ms.
         */
        private Duration closeNotifyFlushTimeout = Duration.ofMillis(3000);

        /**
         * SSL close_notify read timeout. Default to 0 ms.
         */
        private Duration closeNotifyReadTimeout = Duration.ZERO;

        /**
         * The default ssl configuration type. Defaults to TCP.
         */
        private SslProvider.DefaultConfigurationType defaultConfigurationType = SslProvider.DefaultConfigurationType.TCP;

        public X509Certificate[] getTrustedX509CertificatesForTrustManager() {
            try {
                CertificateFactory certificateFactory = CertificateFactory
                        .getInstance("X.509");
                List<Certificate> allCerts = new ArrayList<>();
                for (String trustedCert : ssl.getTrustedX509Certificates()) {
                    try {
                        URL url = ResourceUtils.getURL(trustedCert);
                        Collection<? extends Certificate> certs = certificateFactory
                                .generateCertificates(url.openStream());
                        allCerts.addAll(certs);
                    } catch (IOException e) {
                        throw new WebServerException(
                                "Could not load certificate '" + trustedCert + "'", e);
                    }
                }
                return allCerts.toArray(new X509Certificate[allCerts.size()]);
            } catch (CertificateException e) {
                throw new WebServerException("Could not load CertificateFactory X.509", e);

            }
        }
        public boolean isUseInsecureTrustManager() {
            return useInsecureTrustManager;
        }

        public Ssl setUseInsecureTrustManager(boolean useInsecureTrustManager) {
            this.useInsecureTrustManager = useInsecureTrustManager;
            return this;
        }

        public List<String> getTrustedX509Certificates() {
            return trustedX509Certificates;
        }

        public Ssl setTrustedX509Certificates(List<String> trustedX509Certificates) {
            this.trustedX509Certificates = trustedX509Certificates;
            return this;
        }

        public Duration getHandshakeTimeout() {
            return handshakeTimeout;
        }

        public Ssl setHandshakeTimeout(Duration handshakeTimeout) {
            this.handshakeTimeout = handshakeTimeout;
            return this;
        }

        public Duration getCloseNotifyFlushTimeout() {
            return closeNotifyFlushTimeout;
        }

        public Ssl setCloseNotifyFlushTimeout(Duration closeNotifyFlushTimeout) {
            this.closeNotifyFlushTimeout = closeNotifyFlushTimeout;
            return this;
        }

        public Duration getCloseNotifyReadTimeout() {
            return closeNotifyReadTimeout;
        }

        public Ssl setCloseNotifyReadTimeout(Duration closeNotifyReadTimeout) {
            this.closeNotifyReadTimeout = closeNotifyReadTimeout;
            return this;
        }

        public SslProvider.DefaultConfigurationType getDefaultConfigurationType() {
            return defaultConfigurationType;
        }

        public Ssl setDefaultConfigurationType(SslProvider.DefaultConfigurationType defaultConfigurationType) {
            this.defaultConfigurationType = defaultConfigurationType;
            return this;
        }

        @Override
        public String toString() {
            return "Ssl{" +
                    "useInsecureTrustManager=" + useInsecureTrustManager +
                    ", trustedX509Certificates=" + trustedX509Certificates +
                    ", handshakeTimeout=" + handshakeTimeout +
                    ", closeNotifyFlushTimeout=" + closeNotifyFlushTimeout +
                    ", closeNotifyReadTimeout=" + closeNotifyReadTimeout +
                    ", defaultConfigurationType=" + defaultConfigurationType +
                    '}';
        }
    }
}
